local addon = LibStub("AceAddon-3.0"):GetAddon("Poppins")

local LibMacroParser = LibStub("LibMacroParser-1.0")
local LibButtonFacade = LibStub("LibButtonFacade", true)
local LibKeyBound = LibStub("LibKeyBound-1.0")
local LibSharedMedia = LibStub("LibSharedMedia-3.0")

local Button = {}
addon.Button = Button
local prototype = CreateFrame("CheckButton")
LibStub("AceEvent-3.0"):Embed(prototype)
Button.prototype = prototype
Button.metatable = { __index = prototype }

function prototype:Super(object, ...)
	local super = getmetatable(getmetatable(self).__index).__index
	return super[object](super == prototype and self or super, ...)
end

function prototype:UpdateOptions()
	local parent = self.parent
	local id = parent and parent.id
	local options = parent and parent.options

	if id and parent and options then
		LibButtonFacade = LibButtonFacade or LibStub("LibButtonFacade", true)
		if LibButtonFacade then
			LibButtonFacade:Group(addon.name, parent:GetGroup() or parent.id):AddButton(self)
		end
		self:SetFrameStrata(options.strata)

		local hotkey = self.hotkey
		if options.hotkey.show then
			hotkey:SetJustifyH(options.hotkey.justify)
			hotkey:ClearAllPoints()
			hotkey:SetPoint(options.hotkey.justify, self, "CENTER", options.hotkey.x, options.hotkey.y)
			hotkey:SetFont(LibSharedMedia:Fetch("font", options.hotkey.font), options.hotkey.size, "OUTLINE")
			hotkey:Show()
		else
			hotkey:Hide()
		end
		local count = self.count
		if options.count.show then
			count:SetJustifyH(options.count.justify)
			count:ClearAllPoints()
			count:SetPoint(options.count.justify, self, "CENTER", options.count.x, options.count.y)
			count:SetFont(LibSharedMedia:Fetch("font", options.count.font), options.count.size, "OUTLINE")
			count:Show()
		else
			count:Hide()
		end
		local selected = self.selected
		if options.selected.show then
			selected:SetJustifyH(options.selected.justify)
			selected:ClearAllPoints()
			selected:SetPoint(options.selected.justify, self, "CENTER", options.selected.x, options.selected.y)
			selected:SetFont(LibSharedMedia:Fetch("font", options.selected.font), options.selected.size, "OUTLINE")
			selected:Show()
		else
			selected:Hide()
		end
	else
		self.selected:Hide()
		self.count:Hide()
		self.hotkey:Hide()
		self:RegisterForClicks(nil)
		self:SetFrameStrata("BACKGROUND")
		self:SetScale(1)
		LibButtonFacade = LibButtonFacade or LibStub("LibButtonFacade", true)
		if LibButtonFacade then
			LibButtonFacade:Group(addon.name, parent:GetGroup() or parent.id):RemoveButton(self)
		end
	end
end

prototype.events = {
	-- Shared
	MODIFIER_STATE_CHANGED = true,
	UPDATE_BINDINGS = true,
	PLAYER_ENTERING_WORLD = true,
	-- Spell
	PLAYER_ENTER_COMBAT = true,
	PLAYER_LEAVE_COMBAT = true,
	SPELL_UPDATE_COOLDOWN = true,
	SPELL_UPDATE_USABLE = true,
	START_AUTOREPEAT_SPELL = true,
	STOP_AUTOREPEAT_SPELL = true,
	PLAYER_TARGET_CHANGED = true,
	UNIT_SPELLCAST_SENT = true,
	UNIT_SPELLCAST_STOP = true,
	UNIT_SPELLCAST_FAILED = true,
	UNIT_SPELLCAST_INTERRUPTED = true,
	CURSOR_UPDATE = true,
	CRAFT_SHOW = true,
	CRAFT_CLOSE = true,
	TRADE_SKILL_SHOW = true,
	TRADE_SKILL_CLOSE = true,
	-- Item
	BAG_UPDATE = true,
	BAG_UPDATE_COOLDOWN = true,
	UNIT_INVENTORY_CHANGED = true,
}

function prototype:RegisterAllEvents()
	self:UnregisterAllEvents()
	for event, register in pairs(self.events) do
		if register then
			self:RegisterEvent(event)
		end
	end
end

function prototype:OnAttributeChanged(name, value)
	self.attributes[name] = value
end

function prototype:OnClick()
	self:UpdateState()
end

function prototype:OnShow()
	self:RegisterAllEvents()
	self:Update()
end

function prototype:OnHide()
	self:UnregisterAllEvents()
end

local TOOLTIP_UPDATE_TIME = TOOLTIP_UPDATE_TIME
local ATTACK_BUTTON_FLASH_TIME = ATTACK_BUTTON_FLASH_TIME
function prototype:OnUpdate(elapsed)
	if self.flashing == 1 then
		self.flashtime = self.flashtime - elapsed
		if self.flashtime <= 0 then
			local overtime = -self.flashtime
			if overtime >= ATTACK_BUTTON_FLASH_TIME then
				overtime = 0
			end
			self.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime
			
			local flashTexture = self.flash
			if flashTexture:IsVisible() then
				flashTexture:Hide()
			else
				flashTexture:Show()
			end
		end
	end

	if self.rangeTimer then
		self.rangeTimer = self.rangeTimer - elapsed
		if self.rangeTimer <= 0 then
			local valid
			local type, value = self.type, self.value
			if type == "spell" then
				valid = IsSpellInRange(value)
			elseif type == "item" then
				valid = IsItemInRange(value)
			end
			self.outOfRange = (valid == 0)
			self:UpdateUsable()
			self.rangeTimer = TOOLTIP_UPDATE_TIME
		end
	end

	if self.bagUpdate then
		self.bagUpdate = self.bagUpdate - elapsed
		if self.bagUpdate <= 0 then
			self:UpdateCount()
			self:UpdateUsable(true)
			self.bagUpdate = nil
		end
	end
end

function prototype:OnEnter()
	if self.GetHotkey then
		LibKeyBound:Set(self)
	end
	local tooltips = addon.db.profile.tooltips
	if tooltips == 2 or (tooltips == 3 and not InCombatLockdown()) then
		self:ShowTooltip()
	else
		self:HideTooltip()
	end
end

function prototype:OnLeave()
	self:HideTooltip()
end

function prototype:ShowTooltip()
	if GetCVar("UberTooltips") == "1" then
		GameTooltip_SetDefaultAnchor(GameTooltip, self)
	else
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
	end

	local link, _
	local type, value = self.type, self.value
	if type == "spell" and value then
		link = GetSpellLink(value)
	elseif type == "item" and value then
		_, link = GetItemInfo(value)
	end
	if link and GameTooltip:SetHyperlink(link) then
		self.UpdateTooltip = self.ShowTooltip
	else
		self.UpdateTooltip = nil
	end
end

function prototype:HideTooltip()
	if GameTooltip:GetOwner() == self then
		GameTooltip:Hide()
	end
end

function prototype:MacroUpdate(macro)
	local type, value = self:GetType(true)
	if type == "macro" and value == macro then
		self:Update()
	end
end

function prototype:GetType(real)
	local type = SecureButton_GetModifiedAttribute(self, "type"..self.state)
	local value = type and SecureButton_GetModifiedAttribute(self, type..self.state)
	if not real and type == "macro" and value then
		local oldType, oldValue = self.type, self.value
		if type ~= oldType or value ~= oldValue then
			if oldType == "macro" then
				LibMacroParser.UnregisterCallback(self, oldValue)
			end
			LibMacroParser.RegisterCallback(self, value, "MacroUpdate")
		end
		return LibMacroParser:GetMacroInfo(value)
	end
	return type, value
end

function prototype:Update()
	local type, value = self:GetType()
	local updated
	if type ~= self.type or value ~= self.value then
		self.type, self.value = type, value
		updated = true
	end

	self:UpdateIcon()
	self:UpdateCount()
	self:UpdateHotkeys()
	self:UpdateSelected()
	if value then
		if type == "item" and IsEquippedItem(value) then
			self.border:SetVertexColor(0, 1.0, 0, 0.75)
			self.border:Show()
		else
			self.border:Hide()
		end
		self:UpdateState()
		self:UpdateUsable(true)
		self:UpdateCooldown()
		self:UpdateFlash()
		self:SetScript("OnUpdate", self.OnUpdate)
	else
		self.border:Hide()
		self.cooldown:Hide()
		self:SetScript("OnUpdate", nil)
	end

	if updated then
		--self.callbacks:Fire("OnUpdate", self, type, value)
	end
end

function prototype:UpdateIcon()
	local icon, _
	local type, value = self.type, self.value
	if type and value then
		if type == "spell" then
			icon = GetSpellTexture(value)
		elseif type == "item" then
			icon = GetItemIcon(value)
		elseif type == "macro" then
			_, icon = GetMacroInfo(tonumber(value))
		end
		icon = icon or [[Interface\Icons\INV_Misc_QuestionMark]]
	end

	if icon then
		self.rangeTimer = -1
		self.icon:SetTexture(icon)
		self.icon:Show()
	else
		self.rangeTimer = nil
		self.icon:Hide()
		self.cooldown:Hide()
	end
end

function prototype:UpdateCount()
	local count

	local type, value = self.type, self.value
	if not value then
	elseif type == "spell" and IsConsumableSpell(value) then
		count = GetSpellCount(value)
	elseif type == "item" and IsConsumableItem(value) then
		count = GetItemCount(value, false, true)
	elseif type == "macro" then
		count = GetMacroInfo(tonumber(value))
	end
	self.count:SetText(count)
end

function prototype:UpdateState()
	local checked = 0
	local type, value = self.type, self.value
	if not value then
	elseif type == "spell" then
		checked = (IsCurrentSpell(value) or IsAutoRepeatSpell(value)) and 1 or 0
	elseif type == "item" then
		checked = IsCurrentItem(value) and 1 or 0
	end
	self:SetChecked(checked)
end

function prototype:UpdateUsable(force)
	local isUsable, notEnoughMana
	local icon, hotkey = self.icon, self.hotkey
	if force then 
		icon.state, hotkey.state = 1, 1 
	end

	if self.outOfRange then
		icon:SetVertexColor(0.8, 0.1, 0.1)
	else
		local type, value = self.type, self.value
		if not value then
		elseif type == "spell" then
			isUsable, notEnoughMana = IsUsableSpell(value)
		elseif type == "item" then
			isUsable, notEnoughMana = IsUsableItem(value)
		end

		if isUsable then
			icon:SetVertexColor(1.0, 1.0, 1.0)
		elseif notEnoughMana then
			icon:SetVertexColor(0.5, 0.5, 1.0)
		else
			icon:SetVertexColor(0.4, 0.4, 0.4)
		end
	end
end

function prototype:UpdateCooldown()
	local start, duration, enable

	local type, value = self.type, self.value
	if not value then
	elseif type == "spell" then
		start, duration, enable = GetSpellCooldown(value)
	elseif type == "item" then
		start, duration, enable = GetItemCooldown(value)
	end
	start, duration, enable = start or 0, duration or 0, enable or 0

	CooldownFrame_SetTimer(self.cooldown, start, duration, enable)
end

function prototype:UpdateFlash()
	local type, value = self.type, self.value
	if type == "spell" and value and (IsAttackSpell(value) and IsCurrentSpell(value)) or IsAutoRepeatSpell(value) then
		self:StartFlash()
	else
		self:StopFlash()
	end
end

function prototype:StartFlash()
	local type, value = self.type, self.value
	if type == "spell" and value and IsAttackSpell(value) then
		self.flashing = 1
		self.flashtime = 0
		self:UpdateState()
	end
end

function prototype:StopFlash()
	self.flashing = 0
	self.flash:Hide()
	self:UpdateState()
end

function prototype:UpdateHotkeys()
	local keybinding = self:GetHotkey() or ""
	local hotkey = self.hotkey

	if keybinding == "" then
		hotkey:SetText("")
	else
		hotkey:SetText(LibKeyBound:ToShortKey(keybinding))
	end
end

function prototype:SetKey(keybinding)
	self.parent.settings.keybinding = keybinding
	ClearOverrideBindings(self)
	if key then
		SetOverrideBindingClick(self, 1, keybinding, self:GetName())
	end
	self:UpdateHotkeys()
end

function prototype:GetHotkey()
	local db = self.parent.settings
	return db and db.keybinding
end

function prototype:UpdateSelected()
	self.selected:SetText(self.parent:GetAttribute("state"))
end

function prototype:UPDATE_BINDINGS()
	self:UpdateHotkeys()
end

function prototype:MODIFIER_STATE_CHANGED(modifier, state)
	self:Update()
end

function prototype:BAG_UPDATE()
	local type, value = self.type, self.value
	if type == "item" and value and not self.bagUpdate then
		self.bagUpdate = 0.1
	end
end

function prototype:BAG_UPDATE_COOLDOWN()
	local type, value = self.type, self.value
	if type == "item" and value then
		self:UpdateCooldown()
	end
end

function prototype:UNIT_INVENTORY_CHANGED(event, unit)
	if unit ~= "player" then return end
	local type, value = self.type, self.value
	if type == "item" and value then
		self:Update()
	end
end

function prototype:UNIT_SPELLCAST_SENT(event, unit, spell, rank)
	if unit ~= "player" then return end
	self:UpdateState()
end
prototype.UNIT_SPELLCAST_STOP = prototype.UNIT_SPELLCAST_SENT
prototype.UNIT_SPELLCAST_FAILED = prototype.UNIT_SPELLCAST_SENT
prototype.UNIT_SPELLCAST_INTERRUPTED = prototype.UNIT_SPELLCAST_SENT

function prototype:PLAYER_ENTER_COMBAT()
	self:StartFlash()
end

function prototype:PLAYER_LEAVE_COMBAT()
	self:StopFlash()
end

function prototype:SPELL_UPDATE_COOLDOWN()
	local type, value = self.type, self.value
	if type == "spell" and value then
		self:UpdateCooldown()
	end
end

function prototype:SPELL_UPDATE_USABLE()
	local type, value = self.type, self.value
	if type == "spell" and value then
		self:UpdateUsable()
	end
end

function prototype:START_AUTOREPEAT_SPELL()
	local type, value = self.type, self.value
	if type == "spell" and value and IsAutoRepeatSpell(value) then
		self:StartFlash()
	end
end

function prototype:STOP_AUTOREPEAT_SPELL()
	local type, value = self.type, self.value
	if type == "spell" and value and self.flashing == 1 and not IsAttackSpell(value) then
		self:StopFlash()
	end
end

function prototype:PLAYER_ENTERING_WORLD()
	self:Update()
end

function prototype:PLAYER_TARGET_CHANGED()
	self.rangeTimer = -1
end

function prototype:CRAFT_SHOW()
	self:UpdateState()
end
prototype.CRAFT_CLOSE = prototype.CRAFT_SHOW
prototype.TRADE_SKILL_SHOW = prototype.CRAFT_SHOW
prototype.TRADE_SKILL_CLOSE = prototype.CRAFT_SHOW

function prototype:CURSOR_UPDATE()
	self:UpdateState()
end